
open Format
open Ast

let print fmt ast =

	let base_tab = "\t" in

	let print_list func space lst = 
		ignore(List.map (fun e -> func space e) lst) in
	
	let rec print_list_sep func space sep = function
		| [] -> ()
		| [a] -> func space a; ()
		| a::b -> func space a;
		fprintf fmt "%s" sep; print_list_sep func space sep b in
	
	let rec print_program space = function
		| [] -> ()
		| e::l -> print_sourceElement space e; print_program space l

	and print_sourceElement space = function
		| AStatement(s) -> print_statement space s
		(*| AFunctionDeclaration(funcDecl) -> print_functionDeclaration space funcDecl *) (* Modif #1 *)

	and print_statement space = function
		| ABlock(block) -> print_block space block
		| AVariableStatement(varStmt) -> print_variableStatement space varStmt
		| AConstStatement(constStmt) -> print_variableStatement space constStmt		(* Modif #2 *)
		| AFuncDeclaration(funcDecl) -> print_functionDeclaration space funcDecl	(* Modif #1 *)
		| AEmptyStatement -> fprintf fmt "%s;\n" space; ()
		| AExpressionStatement(expressionStatement) -> print_expressionStatement space expressionStatement
		| AIfStatement(ifStatement) -> print_ifStatement space ifStatement
		| AIterationStatement(iterationStatement) -> print_iterationStatement space iterationStatement
		| AContinueStatement(continueStatement) -> print_continueStatement space continueStatement
		| ABreakStatement(breakStatement) -> print_breakStatement space breakStatement
		| AReturnStatement(returnStatement) -> print_returnStatement space returnStatement
		| AWithStatement(withStatement) -> print_withStatement space withStatement
		| ALabelledStatement(labelledStatement) -> print_labelledStatement space labelledStatement
		| ASwitchStatement(switchStatement) -> print_switchStatement space switchStatement
		| AThrowStatement(throwStatement) -> print_throwStatement space throwStatement
		| ATryStatement(tryStatement) -> print_tryStatement space tryStatement
		| ADebuggerStatement -> fprintf fmt "%sdebugger;\n" space
	
	and print_block space block =
		 fprintf fmt "%s{\n" space;
		 print_list print_statement (space ^ base_tab) block;
		 fprintf fmt "%s}\n" space
	
	and print_variableStatement space (_,_,stmt) =
		fprintf fmt "%svar " space;
		print_variableDeclarationList space stmt;
		fprintf fmt ";\n"
	
	and print_variableDeclarationList space decls =
		print_list_sep print_variableDeclaration space ", " decls;

	and print_variableDeclaration space (ident,init) = match init with
		| None -> fprintf fmt "%s" ident
		| Some init -> fprintf fmt "%s " ident;
			print_initializer space init

	and print_initializer space assignExpr =
		fprintf fmt "= ";
		print_assignmentExpression space assignExpr
	
	and print_assignmentExpression space = function
		| ACondExpr(condExpr) -> print_conditionalExpression space condExpr
		| AAssignOp(lhs, op, assignExpr) ->
			print_leftHandSideExpression space lhs;
			fprintf fmt " %s " op;
			print_assignmentExpression space assignExpr

	and print_leftHandSideExpression space = function
		| ANewExpression(expr) -> print_newExpression space expr
		| ACallExpression(expr) -> print_callExpression space expr
	
	and print_newExpression space = function
		| AMemberExpression(expr) -> print_memberExpression space expr
		| ANewMemberExpression(expr) -> fprintf fmt "new ";
			print_newExpression space expr

	and print_callExpression space = function
		| ACallMemberArgs(expr, args) ->
			print_memberExpression space expr;
			print_arguments space args
		| ACallExpressionArgs(expr, args) ->
			print_callExpression space expr;
			print_arguments space args
		| ACallExpressionExpr(expr1, expr2) -> 
			print_callExpression space expr1;
			fprintf fmt "[";
			print_expression space expr2;
			fprintf fmt "]"
		| ACallExpressionIdent(expr, ident) ->
			print_callExpression space expr;
			fprintf fmt ".%s" ident
	
	and print_memberExpression space = function
		| APrimaryExpr(expr) -> print_primaryExpression space expr
		| AFunctionExpr(funcExpr) -> print_functionExpression space funcExpr
		| AMemberBracketExpr(expr1, expr2) -> 
			print_memberExpression space expr1;
			fprintf fmt "[";
			print_expression space expr2;
			fprintf fmt "]"
		| AMemberDotExpr(expr, ident) ->
			print_memberExpression space expr;
			fprintf fmt ".%s" ident
		| AMemberNewExpr(expr, args) ->
			fprintf fmt "new ";
			print_memberExpression space expr;
			print_arguments space args
	
	and print_arguments space args =
		fprintf fmt "(";
		print_list_sep print_assignmentExpression space ", " args;
		fprintf fmt ")"
	
	and print_functionDeclaration space (_, _, _, ident, params, body) =
		fprintf fmt "\n%sfunction %s (" space ident;
		print_list_sep (fun space e -> fprintf fmt "%s" e) space ", " params;
		fprintf fmt ")\n%s{\n" space;
		print_functionBody (space ^ base_tab) body;
		fprintf fmt "%s}\n\n" space;
	
	and print_functionExpression space (_, ident, params, body) =
		fprintf fmt "function %s(" (match ident with 
			| None -> ""
			| Some ident -> ident ^ " ");
		print_list_sep (fun space e -> fprintf fmt "%s" e) space ", " params;
		fprintf fmt ") {\n";
		print_functionBody (space ^ base_tab) body;
		fprintf fmt "%s}" space
	
	and print_functionBody space body =
		print_list print_sourceElement space body
	
	and print_expressionStatement space expr =
		fprintf fmt "%s" space;
		print_expression space expr;
		fprintf fmt ";\n"
	
	and print_ifStatement space = function
		| AIfElse(expr, stmt1, stmt2) ->
			fprintf fmt "%sif (" space;
			print_expression space expr;
			fprintf fmt ")\n";
			(match stmt1 with
				| ABlock _ -> print_statement space stmt1
				| _ -> print_statement (space ^ base_tab) stmt1);
			fprintf fmt "%selse\n" space;
			(match stmt2 with
				| ABlock _ -> print_statement space stmt2
				| _ -> print_statement (space ^ base_tab) stmt2)
		| AIf(expr, stmt) ->
			fprintf fmt "%sif (" space;
			print_expression space expr;
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)

	and print_iterationStatement space = function
		| ADoWhile(stmt, expr) ->
			fprintf fmt "%sdo\n" space;
				(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt);
			fprintf fmt "%swhile (" space;
			print_expression space expr;
			fprintf fmt ");\n"
		| AWhile(expr, stmt) ->
			fprintf fmt "%swhile (" space;
			print_expression space expr;
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)
		| AFor(expr1, expr2, expr3, stmt) ->
			fprintf fmt "%sfor (" space;
			(match expr1 with
				| None -> fprintf fmt "; "
				| Some expr1 -> print_expression space expr1; fprintf fmt "; ");
			(match expr2 with
				| None -> fprintf fmt "; "
				| Some expr2 -> print_expression space expr2; fprintf fmt "; ");
			(match expr3 with
				| None -> ()
				| Some expr3 -> print_expression space expr3);
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)
		| AForIn(lhs, expr, stmt) ->
			fprintf fmt "%sfor (" space;
			print_leftHandSideExpression space lhs;
			fprintf fmt " in ";
			print_expression space expr;
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)
		| AForVar(_, varDecls, expr1, expr2, stmt) ->
			fprintf fmt "%sfor (var " space;
			print_variableDeclarationList space varDecls;
			fprintf fmt "; ";
			(match expr1 with
				| None -> fprintf fmt "; "
				| Some expr1 -> print_expression space expr1; fprintf fmt "; ");
			(match expr2 with
				| None -> ()
				| Some expr2 -> print_expression space expr2);
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)
		| AForVarIn(_, varDecl, expr, stmt) ->
			fprintf fmt "%sfor (var " space;
			print_variableDeclaration space varDecl;
			fprintf fmt " in ";
			print_expression space expr;
			fprintf fmt ")\n";
			(match stmt with
				| ABlock _ -> print_statement space stmt
				| _ -> print_statement (space ^ base_tab) stmt)
	
	and print_continueStatement space = function
		| None -> fprintf fmt "%scontinue;\n" space
		| Some ident -> fprintf fmt "%scontinue %s;\n" space ident

	and print_breakStatement space = function
		| None -> fprintf fmt "%sbreak;\n" space
		| Some ident -> fprintf fmt "%sbreak %s;\n" space ident

	and print_returnStatement space = function
		| None -> fprintf fmt "%sreturn;\n" space
		| Some expr -> fprintf fmt "%sreturn " space;
			print_expression space expr;
			fprintf fmt ";\n"
	
	and print_withStatement space (expr, stmt) = 
		fprintf fmt "%swith (" space;
		print_expression space expr;
		fprintf fmt ")\n";
		(match stmt with
			| ABlock _ -> print_statement space stmt
			| _ -> print_statement (space ^ base_tab) stmt)
	
	and print_labelledStatement space (ident, stmt) =
		fprintf fmt "%s%s:\n" space ident;
		print_statement (space ^ base_tab) stmt

	and print_switchStatement space (expr, caseBlock) = 
		fprintf fmt "%sswitch (" space;
		print_expression space expr;
		fprintf fmt ")\n";
		print_caseBlock space caseBlock
	
	and print_caseBlock space = function
		| ACasesNoDefault(caseClauses) -> 
			fprintf fmt "%s{\n" space;
			print_caseClauses space caseClauses;
			fprintf fmt "%s}\n" space;
		| ACasesDefault(caseClauses1, defaultClause, caseClauses2) ->
			fprintf fmt "%s{\n" space;
			print_caseClauses space caseClauses1;
			print_defaultClause (space ^ base_tab) defaultClause;
			print_caseClauses space caseClauses2;
			fprintf fmt "%s}\n" space;

	and print_defaultClause space stmts =
		fprintf fmt "%sdefault:\n" space;
		print_list print_statement (space ^ base_tab) stmts;
		fprintf fmt "\n"

	and print_caseClauses space clauses = 
		print_list print_caseClause (space ^ base_tab) clauses;
		
	and print_caseClause space (expr,stmts) =
		fprintf fmt "%scase " space;
		print_expression space expr;
		fprintf fmt " :\n";
		print_list print_statement (space ^ base_tab) stmts;
		fprintf fmt "\n"
	
	and print_throwStatement space expr =
		fprintf fmt "%sthrow " space;
		print_expression space expr;
		fprintf fmt ";\n"
	
	and print_tryStatement space = function
		| ATryCatch(block, catch) ->
			fprintf fmt "%stry\n" space;
			print_block space block;
			print_catch space catch
		| ATryFinally(block, finally) ->
			fprintf fmt "%stry\n" space;
			print_block space block;
			print_finally space finally
		| ATryCatchFinally(block, catch, finally) ->
			fprintf fmt "%stry\n" space;
			print_block space block;
			print_catch space catch;
			print_finally space finally
	
	and print_catch space (ident,block) =
		fprintf fmt "%scatch (%s)\n" space ident;
		print_block space block;
	
	and print_finally space block =
		fprintf fmt "%sfinally\n" space;
		print_block space block
	
	and print_conditionalExpression space = function
		| ACondBinaryExpr(expr) -> print_binaryExpression space expr
		| ACondTernaryExpr(expr, assignExpr1, assignExpr2) ->
			print_binaryExpression space expr;
			fprintf fmt " ? ";
			print_assignmentExpression space assignExpr1;
			fprintf fmt " : ";
			print_assignmentExpression space assignExpr2
	
	and print_binaryExpression space = function
		| AUnaryExpr(unaryExpression) -> print_unaryExpression space unaryExpression
		| ABinaryExpr(op, expr1, expr2) ->
			print_binaryExpression space expr1;
			fprintf fmt " %s " op;
			print_binaryExpression space expr2
	
	and print_unaryExpression space = function
		| APostfixExpr(expr) -> print_postfixExpression space expr;
		| AOpUnaryExpr(op, expr) -> fprintf fmt "%s " op;
		print_unaryExpression space expr
	
	and print_postfixExpression space = function
		| ALeftHandSide(lhs) -> print_leftHandSideExpression space lhs
		| ALeftHandSideOp(lhs, op) ->
			print_leftHandSideExpression space lhs;
			fprintf fmt " %s" op
	
	and print_expression space expr =
		print_list_sep print_assignmentExpression space ", " expr
	
	and print_primaryExpression space = function
		| AThis -> fprintf fmt "this"
		| AIdentifier(identifier) -> fprintf fmt "%s" identifier
		| ALiteral(literal) -> print_literal space literal
		| AArrayLiteral(arrayLiteral) -> print_arrayLiteral space arrayLiteral
		| AObjectLiteral(objectLiteral) -> print_objectLiteral space objectLiteral
		| AExpression(expr) -> fprintf fmt "("; print_expression space expr; fprintf fmt ")"

	and print_literal space = function
		| ANull -> fprintf fmt "null"
		| ABooleanLiteral(boolean) -> fprintf fmt "%s" (if boolean then "true" else "false")
		| ALitNumericLiteral(numeric) -> fprintf fmt "%s" numeric
		| ALitStringLiteral(str) -> fprintf fmt "\"%s\"" ((Str.global_replace (Str.regexp_string "\"") "\\\"") str)
		| ARegularExpressionLiteral(regexp) -> print_regularExpression space regexp
	
	and print_regularExpression space (str1, str2) = 
		fprintf fmt "/%s/%s" ((Str.global_replace (Str.regexp_string "/") "\\/") str1) str2

	and print_arrayLiteral space = function
		| AElisions(elisions) -> fprintf fmt "[";
			print_elisions space elisions;
			fprintf fmt "]"
		| AElements(elements) -> fprintf fmt "[";
			print_elements space elements;
			fprintf fmt "]"
		| AElementsAndElisions(elements, elisions) ->
			fprintf fmt "[";
			print_elements space elements;
			print_elisions space elisions;
			fprintf fmt "]"
	
	and print_elisions space = function
		| [] -> ()
		| a::b -> fprintf fmt ","; print_elisions space b
	
	and print_elements space elements =
		print_list_sep print_element space ", " elements
	
	and print_element space (elisions, expr) =
		print_elisions space elisions;
		print_assignmentExpression space expr
	
	and print_objectLiteral space objectLiteral =
		fprintf fmt "{%s\n%s" (if List.length objectLiteral > 0 then "\n" else "") (space ^ base_tab);
		print_list_sep print_propertyAssignment (space ^ base_tab) (",\n\n" ^ space ^ base_tab) objectLiteral;
		fprintf fmt "\n%s}" space
	
	and print_propertyName space = function
		| AIdentifierName(str) -> fprintf fmt "%s" str
		| AStringLiteral(str) -> fprintf fmt "\"%s\"" ((Str.global_replace (Str.regexp_string "\"") "\\\"") str)
		| ANumericLiteral(numeric) -> fprintf fmt "%s" numeric

	and print_propertyAssignment space = function
		| APropAssignment(propertyName, assignExpr) -> 
			print_propertyName space propertyName;
			fprintf fmt " : ";
			print_assignmentExpression space assignExpr
		| APropGet(propertyName, functionBody) ->
			fprintf fmt "get ";
			print_propertyName space propertyName;
			fprintf fmt "()\n%s{\n" space;
			print_functionBody (space ^ base_tab) functionBody;
			fprintf fmt "%s}" space
			
		| APropSet(propertyName, idents, functionBody) ->
			fprintf fmt "set ";
			print_propertyName space propertyName;
			fprintf fmt "(";
			print_list_sep (fun space e -> fprintf fmt "%s" e) (space ^ base_tab) ", " idents;
			fprintf fmt ")\n%s{\n" space;
			print_functionBody (space ^ base_tab) functionBody;
			fprintf fmt "%s}" space

	in
		print_program "" ast
